"""
@Author    : ghenyar
@Time      : 2025/9/4 17:01
@File      : pay
@Desc      : 微信支付
"""

import json
import os
import time
import random
from datetime import datetime
from typing import Dict, Any, Optional
from urllib.parse import urljoin

import requests

from . import decrypt, random_charts

from base64 import b64encode
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding


class Pay:
    def __init__(self,config: dict, pay_desc: str = ""):
        """微信支付初始化。
        :param config: 配置字典，至少应包含:
                - "app_id": 小程序/公众号的 app_id
                - "mchid": 微信支付商户号
                - "APIv3": APIv3 密钥
                - "notify_url": 支付成功回调地址
                - "private_key": 商户私钥内容或文件路径
                - "serial_no": 商户证书序列号
                - "key_type": 密钥类型 (0: 文件路径, 1: 字符串内容)
                - "refund_notify_url": (可选) 退款回调地址
        :param pay_desc: 默认的支付描述
        """
        self.base_url ="https://api.mch.weixin.qq.com"
        self.app_id = config['app_id']
        self.mch_id = config["mchid"]
        self.apikey = config["APIv3"]
        self.notify_url = config["notify_url"]
        self.refunds_notify_url = config.get("refund_notify_url", "")
        self.serial_no = config["serial_no"]
        self.pay_desc = pay_desc

        # 使用 Session 进行请求，可以复用连接，提升性能
        self._session = requests.Session()
        # 获取秘钥
        self.private_key=self._load_private_key(config["private_key"],config["key_type"])

    @staticmethod
    def _load_private_key(key_content:str,key_type:int):
        """
        从文件或字符串加载商户私钥。
        :param key_content: 私钥的文件路径或字符串内容。
        :param key_type: 0 表示 key_content 是文件路径，1 表示是字符串。
        :return: cryptography 的私钥对象。
        """
        if key_type == 0:
            # key_content 是文件路径
            # 建议使用绝对路径或相对于项目根目录的路径以增加健壮性
            full_path = os.path.join(os.getcwd(), key_content)
            with open(full_path, "r", encoding="utf-8") as file:
                key_str = file.read()
        else:
            # key_content 是私钥字符串本身
            key_str = key_content

        return serialization.load_pem_private_key(
            key_str.encode("utf-8"),
            password=None,  # 如果私钥有密码，请在此处提供
            backend=default_backend()
        )

    def jsapi(self,open_id: str, total: int, order_sn: Optional[str] = None,
                  order_sn_prefix: str = "ORDER_")-> Dict[str, Any]:
        """
         JSAPI/小程序 统一下单接口。
         :param open_id: 用户的 openid。
         :param total: 支付金额，单位为分。
         :param order_sn: 可选，外部传入的商户订单号。
         :param order_sn_prefix: 如果未提供 order_sn，则以此为前缀生成新订单号。
         :return: 包含 prepay_id 和 order_sn 的字典，或错误信息。
         """
        url_path = "/v3/pay/transactions/jsapi"

        if not order_sn:
            order_sn = self._create_order_sn(order_sn_prefix)

        body = {
            "appid": self.app_id,
            "mchid": self.mch_id,
            "description": self.pay_desc,
            "out_trade_no": order_sn,
            "notify_url": self.notify_url,
            "amount": {"total": total, "currency": "CNY"},
            "payer": {"openid": open_id},
            "settle_info": {"profit_sharing": False},
        }

        response = self._make_post_request(url_path, body)
        if "error_code" in response:
            return response

        return {"prepay_id": response.get("prepay_id"), "order_sn": order_sn}

    def pay_api(self,prepay_id: str) -> Dict[str, str]:
        """
        生成小程序/JSAPI调起支付所需的参数。
        :param prepay_id: `jsapi` 方法返回的预支付交易会话标识。
        :return: 一个字典，包含前端 `wx.requestPayment` 需要的所有参数。
        """
        timestamp = str(int(time.time()))
        nonce_str = random_charts()
        package = f"prepay_id={prepay_id}"

        # 构造签名所需的数据
        data_to_sign = f"{self.app_id}\n{timestamp}\n{nonce_str}\n{package}\n"

        # 计算签名
        pay_sign = self._calculate_rsa_sign(data_to_sign)

        return {
            "timeStamp": timestamp,
            "nonceStr": nonce_str,
            "package": package,
            "signType": "RSA",
            "paySign": pay_sign
        }

    def query_pay_order(self,order_sn: str, use_wx_order_id: bool = False) -> Dict[str, Any]:
        """
        查询订单支付状态。
        :param order_sn: 商户订单号或微信支付订单号。
        :param use_wx_order_id: True 表示 order_sn 是微信支付订单号，False 表示是商户订单号。
        :return: 包含交易状态和描述的字典。
        """

        if use_wx_order_id:
            url_path = f"/v3/pay/transactions/id/{order_sn}"
        else:
            url_path = f"/v3/pay/transactions/out-trade-no/{order_sn}"

        url_path_with_params = f"{url_path}?mchid={self.mch_id}"

        response = self._make_get_request(url_path_with_params)
        if "error_code" in response:
            return response
        return {"trade_state": response.get("trade_state"), "message": response.get("trade_state_desc")}

    def close_order(self,order_sn:str) -> Optional[Dict[str, Any]]:
        """
        关闭订单。
        :param order_sn: 商户订单号。
        :return: 成功时无返回(None)，失败时返回错误信息。
        """
        url_path = f"/v3/pay/transactions/out-trade-no/{order_sn}/close"
        body = {"mchid": self.mch_id}
        return self._make_post_request(url_path, body)

    def refunds(self, order_sn: str, out_refund_no: str, refund_money: int, total: int,
                    use_wx_order_id: bool = False, reason: Optional[str] = None)->Dict[str, Any]:
        """
        申请退款。
        :param order_sn: 原支付交易的订单号。
        :param out_refund_no: 商户系统内部的退款单号。
        :param refund_money: 退款金额，单位分。
        :param total: 原订单金额，单位分。
        :param use_wx_order_id: order_sn 的类型，True-微信订单号，False-商户订单号。
        :param reason: 退款原因。
        :return: 退款申请的响应结果。
        """
        url_path = "/v3/refund/domestic/refunds"
        body = {
            "out_refund_no": out_refund_no,
            "reason": reason,
            "notify_url": self.refunds_notify_url,
            "amount": {
                "refund": refund_money,
                "total": total,
                "currency": "CNY"
            },
        }
        if use_wx_order_id:
            body["transaction_id"] = order_sn
        else:
            body["out_trade_no"] = order_sn

        return self._make_post_request(url_path, body)

    def decrypt_notify(self, notify_data: dict) -> Dict[str, Any]:
        """
        解密微信支付回调通知中的加密报文。
        :param notify_data: 微信回调的原始 JSON 数据。
        :return: 解密后的数据字典。
        """
        resource = notify_data.get('resource', {})
        ciphertext = resource.get('ciphertext')
        associated_data = resource.get('associated_data')
        nonce = resource.get('nonce')

        # 使用工具函数解密
        decrypted_result = decrypt(api_key=self.apikey, nonce=nonce, ciphertext=ciphertext,
                                   associated_data=associated_data)
        return json.loads(decrypted_result)

    def _calculate_rsa_sign(self, data: str) -> str:
        """使用实例的私钥计算RSA签名(SHA256 with RSA)"""
        signature = self.private_key.sign(
            data.encode("utf-8"),
            padding.PKCS1v15(),
            hashes.SHA256()
        )
        return b64encode(signature).decode("utf-8")

    def _create_auth_header(self, method: str, url: str, body: str) -> str:
        """为 APIv3 请求生成认证头 (Authorization)"""
        timestamp = str(int(time.time()))
        randoms = random_charts()

        # 构造待签名字符串
        sign_list = [method.upper(), url, timestamp, randoms, body]
        sign_str = "\n".join(sign_list) + "\n"

        # 计算签名
        signature = self._calculate_rsa_sign(sign_str)

        # 拼接 Authorization 头部
        return (
            f'WECHATPAY2-SHA256-RSA2048 '
            f'mchid="{self.mch_id}",'
            f'nonce_str="{randoms}",'
            f'timestamp="{timestamp}",'
            f'serial_no="{self.serial_no}",'
            f'signature="{signature}"'
        )

    def _make_get_request(self, url_path: str, headers: Optional[Dict] = None) -> Any:
        """封装GET请求"""
        return self._send_request("GET", url_path, extra_headers=headers)

    def _make_post_request(self, url_path: str, body: Dict, headers: Optional[Dict] = None) -> Any:
        """封装POST请求"""
        return self._send_request("POST", url_path, body=body, extra_headers=headers)

    def _send_request(self, method: str, url_path: str, body: Optional[Dict] = None,
                      extra_headers: Optional[Dict] = None) -> Any:

        # 请求地址
        full_url = urljoin(self.base_url, url_path)
        # 对于POST请求，body需转为json字符串；对于GET，body为空
        body_str = json.dumps(body, separators=(',', ':')) if body else ""

        # Authorization 头部
        authorization = self._create_auth_header(method, url_path, body_str)
        headers = {
            "Accept": "application/json",
            "Content-Type": "application/json; charset=utf-8",
            "Authorization": authorization,
        }
        if extra_headers:
            headers.update(extra_headers)

        try:
            response = self._session.request(
                method=method,
                url=full_url,
                headers=headers,
                data=body_str.encode('utf-8') if body_str else None,
                timeout=10  # 设置10秒超时
            )
            # 204 No Content 表示成功但无返回体
            if response.status_code == 204:
                return True

            response_json = response.json()
            if not response.ok:  # 状态码不在 200-299 范围
                return {"error_code": response_json.get("code"), "message": response_json.get("message")}

            return response_json
        except json.JSONDecodeError:
            return {"error_code": "INVALID_JSON", "message": f"Response is not valid JSON: {response.text[:200]}"}
        except requests.exceptions.RequestException as e:
            return {"error_code": "NETWORK_ERROR", "message": str(e)}

    @staticmethod
    def _create_order_sn(order_sn_prefix: str = "WX_"):
        """
        创建订单号(前缀 + YYYYMMDDHHMMSS + 4位随机数)
        :param order_sn_prefix: 订单号前缀
        :return:
        """
        now = datetime.now()
        random_number_string = "".join(str(random.randint(0, 9)) for _ in range(4))
        order_sn = order_sn_prefix + now.strftime("%Y%m%d%H%M%S") + str(random_number_string)
        return order_sn

